/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

var _util = require("static/plugins/js/zrender/lib/core/util");

var createHashMap = _util.createHashMap;
var each = _util.each;
var isString = _util.isString;
var defaults = _util.defaults;
var extend = _util.extend;
var isObject = _util.isObject;
var clone = _util.clone;

var _model = require("../../util/model");

var normalizeToArray = _model.normalizeToArray;

var _sourceHelper = require("./sourceHelper");

var guessOrdinal = _sourceHelper.guessOrdinal;
var BE_ORDINAL = _sourceHelper.BE_ORDINAL;

var Source = require("../Source");

var _dimensionHelper = require("./dimensionHelper");

var OTHER_DIMENSIONS = _dimensionHelper.OTHER_DIMENSIONS;

var DataDimensionInfo = require("../DataDimensionInfo");

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @deprecated
 * Use `echarts/data/helper/createDimensions` instead.
 */

/**
 * @see {module:echarts/test/ut/spec/data/completeDimensions}
 *
 * This method builds the relationship between:
 * + "what the coord sys or series requires (see `sysDims`)",
 * + "what the user defines (in `encode` and `dimensions`, see `opt.dimsDef` and `opt.encodeDef`)"
 * + "what the data source provids (see `source`)".
 *
 * Some guess strategy will be adapted if user does not define something.
 * If no 'value' dimension specified, the first no-named dimension will be
 * named as 'value'.
 *
 * @param {Array.<string>} sysDims Necessary dimensions, like ['x', 'y'], which
 *      provides not only dim template, but also default order.
 *      properties: 'name', 'type', 'displayName'.
 *      `name` of each item provides default coord name.
 *      [{dimsDef: [string|Object, ...]}, ...] dimsDef of sysDim item provides default dim name, and
 *                                    provide dims count that the sysDim required.
 *      [{ordinalMeta}] can be specified.
 * @param {module:echarts/data/Source|Array|Object} source or data (for compatibal with pervious)
 * @param {Object} [opt]
 * @param {Array.<Object|string>} [opt.dimsDef] option.series.dimensions User defined dimensions
 *      For example: ['asdf', {name, type}, ...].
 * @param {Object|HashMap} [opt.encodeDef] option.series.encode {x: 2, y: [3, 1], tooltip: [1, 2], label: 3}
 * @param {Function} [opt.encodeDefaulter] Called if no `opt.encodeDef` exists.
 *      If not specified, auto find the next available data dim.
 *      param source {module:data/Source}
 *      param dimCount {number}
 *      return {Object} encode Never be `null/undefined`.
 * @param {string} [opt.generateCoord] Generate coord dim with the given name.
 *      If not specified, extra dim names will be:
 *      'value', 'value0', 'value1', ...
 * @param {number} [opt.generateCoordCount] By default, the generated dim name is `generateCoord`.
 *      If `generateCoordCount` specified, the generated dim names will be:
 *      `generateCoord` + 0, `generateCoord` + 1, ...
 *      can be Infinity, indicate that use all of the remain columns.
 * @param {number} [opt.dimCount] If not specified, guess by the first data item.
 * @return {Array.<module:data/DataDimensionInfo>}
 */
function completeDimensions(sysDims, source, opt) {
    if (!Source.isInstance(source)) {
        source = Source.seriesDataToSource(source);
    }

    opt = opt || {};
    sysDims = (sysDims || []).slice();
    var dimsDef = (opt.dimsDef || []).slice();
    var dataDimNameMap = createHashMap();
    var coordDimNameMap = createHashMap(); // var valueCandidate;

    var result = [];
    var dimCount = getDimCount(source, sysDims, dimsDef, opt.dimCount); // Apply user defined dims (`name` and `type`) and init result.

    for (var i = 0; i < dimCount; i++) {
        var dimDefItem = (dimsDef[i] = extend(
            {},
            isObject(dimsDef[i])
                ? dimsDef[i]
                : {
                      name: dimsDef[i],
                  }
        ));
        var userDimName = dimDefItem.name;
        var resultItem = (result[i] = new DataDimensionInfo()); // Name will be applied later for avoiding duplication.

        if (userDimName != null && dataDimNameMap.get(userDimName) == null) {
            // Only if `series.dimensions` is defined in option
            // displayName, will be set, and dimension will be diplayed vertically in
            // tooltip by default.
            resultItem.name = resultItem.displayName = userDimName;
            dataDimNameMap.set(userDimName, i);
        }

        dimDefItem.type != null && (resultItem.type = dimDefItem.type);
        dimDefItem.displayName != null &&
            (resultItem.displayName = dimDefItem.displayName);
    }

    var encodeDef = opt.encodeDef;

    if (!encodeDef && opt.encodeDefaulter) {
        encodeDef = opt.encodeDefaulter(source, dimCount);
    }

    encodeDef = createHashMap(encodeDef); // Set `coordDim` and `coordDimIndex` by `encodeDef` and normalize `encodeDef`.

    encodeDef.each(function (dataDims, coordDim) {
        dataDims = normalizeToArray(dataDims).slice(); // Note: It is allowed that `dataDims.length` is `0`, e.g., options is
        // `{encode: {x: -1, y: 1}}`. Should not filter anything in
        // this case.

        if (
            dataDims.length === 1 &&
            !isString(dataDims[0]) &&
            dataDims[0] < 0
        ) {
            encodeDef.set(coordDim, false);
            return;
        }

        var validDataDims = encodeDef.set(coordDim, []);
        each(dataDims, function (resultDimIdx, idx) {
            // The input resultDimIdx can be dim name or index.
            isString(resultDimIdx) &&
                (resultDimIdx = dataDimNameMap.get(resultDimIdx));

            if (resultDimIdx != null && resultDimIdx < dimCount) {
                validDataDims[idx] = resultDimIdx;
                applyDim(result[resultDimIdx], coordDim, idx);
            }
        });
    }); // Apply templetes and default order from `sysDims`.

    var availDimIdx = 0;
    each(sysDims, function (sysDimItem, sysDimIndex) {
        var coordDim;
        var sysDimItem;
        var sysDimItemDimsDef;
        var sysDimItemOtherDims;

        if (isString(sysDimItem)) {
            coordDim = sysDimItem;
            sysDimItem = {};
        } else {
            coordDim = sysDimItem.name;
            var ordinalMeta = sysDimItem.ordinalMeta;
            sysDimItem.ordinalMeta = null;
            sysDimItem = clone(sysDimItem);
            sysDimItem.ordinalMeta = ordinalMeta; // `coordDimIndex` should not be set directly.

            sysDimItemDimsDef = sysDimItem.dimsDef;
            sysDimItemOtherDims = sysDimItem.otherDims;
            sysDimItem.name =
                sysDimItem.coordDim =
                sysDimItem.coordDimIndex =
                sysDimItem.dimsDef =
                sysDimItem.otherDims =
                    null;
        }

        var dataDims = encodeDef.get(coordDim); // negative resultDimIdx means no need to mapping.

        if (dataDims === false) {
            return;
        }

        var dataDims = normalizeToArray(dataDims); // dimensions provides default dim sequences.

        if (!dataDims.length) {
            for (
                var i = 0;
                i < ((sysDimItemDimsDef && sysDimItemDimsDef.length) || 1);
                i++
            ) {
                while (
                    availDimIdx < result.length &&
                    result[availDimIdx].coordDim != null
                ) {
                    availDimIdx++;
                }

                availDimIdx < result.length && dataDims.push(availDimIdx++);
            }
        } // Apply templates.

        each(dataDims, function (resultDimIdx, coordDimIndex) {
            var resultItem = result[resultDimIdx];
            applyDim(defaults(resultItem, sysDimItem), coordDim, coordDimIndex);

            if (resultItem.name == null && sysDimItemDimsDef) {
                var sysDimItemDimsDefItem = sysDimItemDimsDef[coordDimIndex];
                !isObject(sysDimItemDimsDefItem) &&
                    (sysDimItemDimsDefItem = {
                        name: sysDimItemDimsDefItem,
                    });
                resultItem.name = resultItem.displayName =
                    sysDimItemDimsDefItem.name;
                resultItem.defaultTooltip =
                    sysDimItemDimsDefItem.defaultTooltip;
            } // FIXME refactor, currently only used in case: {otherDims: {tooltip: false}}

            sysDimItemOtherDims &&
                defaults(resultItem.otherDims, sysDimItemOtherDims);
        });
    });

    function applyDim(resultItem, coordDim, coordDimIndex) {
        if (OTHER_DIMENSIONS.get(coordDim) != null) {
            resultItem.otherDims[coordDim] = coordDimIndex;
        } else {
            resultItem.coordDim = coordDim;
            resultItem.coordDimIndex = coordDimIndex;
            coordDimNameMap.set(coordDim, true);
        }
    } // Make sure the first extra dim is 'value'.

    var generateCoord = opt.generateCoord;
    var generateCoordCount = opt.generateCoordCount;
    var fromZero = generateCoordCount != null;
    generateCoordCount = generateCoord ? generateCoordCount || 1 : 0;
    var extra = generateCoord || "value"; // Set dim `name` and other `coordDim` and other props.

    for (var resultDimIdx = 0; resultDimIdx < dimCount; resultDimIdx++) {
        var resultItem = (result[resultDimIdx] =
            result[resultDimIdx] || new DataDimensionInfo());
        var coordDim = resultItem.coordDim;

        if (coordDim == null) {
            resultItem.coordDim = genName(extra, coordDimNameMap, fromZero);
            resultItem.coordDimIndex = 0;

            if (!generateCoord || generateCoordCount <= 0) {
                resultItem.isExtraCoord = true;
            }

            generateCoordCount--;
        }

        resultItem.name == null &&
            (resultItem.name = genName(resultItem.coordDim, dataDimNameMap));

        if (
            resultItem.type == null &&
            (guessOrdinal(source, resultDimIdx, resultItem.name) ===
                BE_ORDINAL.Must || // Consider the case:
                // {
                //    dataset: {source: [
                //        ['2001', 123],
                //        ['2002', 456],
                //        ...
                //        ['The others', 987],
                //    ]},
                //    series: {type: 'pie'}
                // }
                // The first colum should better be treated as a "ordinal" although it
                // might not able to be detected as an "ordinal" by `guessOrdinal`.
                (resultItem.isExtraCoord &&
                    (resultItem.otherDims.itemName != null ||
                        resultItem.otherDims.seriesName != null)))
        ) {
            resultItem.type = "ordinal";
        }
    }

    return result;
} // ??? TODO
// Originally detect dimCount by data[0]. Should we
// optimize it to only by sysDims and dimensions and encode.
// So only necessary dims will be initialized.
// But
// (1) custom series should be considered. where other dims
// may be visited.
// (2) sometimes user need to calcualte bubble size or use visualMap
// on other dimensions besides coordSys needed.
// So, dims that is not used by system, should be shared in storage?

function getDimCount(source, sysDims, dimsDef, optDimCount) {
    // Note that the result dimCount should not small than columns count
    // of data, otherwise `dataDimNameMap` checking will be incorrect.
    var dimCount = Math.max(
        source.dimensionsDetectCount || 1,
        sysDims.length,
        dimsDef.length,
        optDimCount || 0
    );
    each(sysDims, function (sysDimItem) {
        var sysDimItemDimsDef = sysDimItem.dimsDef;
        sysDimItemDimsDef &&
            (dimCount = Math.max(dimCount, sysDimItemDimsDef.length));
    });
    return dimCount;
}

function genName(name, map, fromZero) {
    if (fromZero || map.get(name) != null) {
        var i = 0;

        while (map.get(name + i) != null) {
            i++;
        }

        name += i;
    }

    map.set(name, true);
    return name;
}

var _default = completeDimensions;
module.exports = _default;
